package com.recharge.service.impl;


import com.recharge.common.enums.ErrorCode;
import com.recharge.common.result.ResultDO;
import com.recharge.common.result.ResultSupport;
import com.recharge.common.util.*;
import com.recharge.domain.context.RedisMessageSerializer;
import com.recharge.domain.vo.*;
import com.recharge.service.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.print.DocFlavor;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.*;


@Service
public class RechargeServiceImpl implements RechargeService, MessageListener {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
    /**
     * 39
     */
    //private static final int maxOrderThreadNumber = 50;

    private static final int maxOrderThreadNumber = 20;

    @Autowired
    private OrderService orderService;
    @Autowired
    private MerchantService merchantService;
    @Autowired
    private GoodsService goodsService;
    @Autowired
    private ProviderOrderService providerOrderService;
    @Autowired
    private ProviderApiService providerApiService;
    @Autowired
    private GoodsMerchantService goodsMerchantService;
    @Autowired
    MerchantAccountService merchantAccountService;
    @Autowired
    private CardVoucherService cardVoucherService;
    @Autowired
    private VoucherRecordService voucherRecordService;
    @Autowired
    private RouteFilterService routeFilterService;
    @Autowired
    private TransactionService transactionService;
    @Autowired
    private RefundService refundService;

    private ThreadPoolExecutor executorService = null;

    private ThreadPoolExecutor sendExecService = null;

    @Autowired
    private RedisTemplate<String, Object> redisMessageTemplate;

    private BlockingQueue<Thread> threadQueue = new LinkedBlockingQueue<>();

    @Autowired
    private RedisUtil redisUtil;

    @Value("${node.name}")
    private String nodeName;
    @PostConstruct
    public void init() {
        executorService =  new ThreadPoolExecutor(10, maxOrderThreadNumber,
                600,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(50000),
                new ThreadPoolExecutor.DiscardPolicy());

        sendExecService =  new ThreadPoolExecutor(10, maxOrderThreadNumber,
                600,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(50000),
                new ThreadPoolExecutor.DiscardPolicy());

    }

    public ResultDO getHandlerOrderSize() {
        ResultDO result = new ResultSupport();
        result.setModel(ResultDO.FIRST_MODEL_KEY, executorService.getQueue().size());
        return result;
    }



    @Override
    public void onMessage(Message message, byte[] bytes) {
       executorService.execute(()-> {
        RechargeDO domain = (RechargeDO) RedisMessageSerializer.deserialize(message.getBody());
        ErrorCode errorCode = null;
        OrderDO order = new OrderDO();
        order.setRechargeNumber(domain.getRechargeNumber());
        order.setGoodsNo(domain.getGoodsNo());
        order.setAmount(domain.getPrice());
        order.setGoodsQuantity(domain.getGoodsQuantity());
        order.setMerchantNo(domain.getMerchantNo());
        order.setMerchantId(domain.getMerchantId());
        order.setMerchantName(domain.getMerchantName());
        order.setMerchantSerialNumber(domain.getSerialNumber());
        order.setOrderNo(domain.getOrderNo());
        order.setCallback(domain.getCallbackUrl());
        order.setNodeName(domain.getNodeName());
        order.setCreateDate(Calendar.getInstance().getTime());
        if (domain.getRetryStatus().intValue() == Constant.ORDER_RETRY_STATUS_0) {
            order.setId(SnowflakeIdWorker.getNo());
        } else {
        order.setId(domain.getOrderId());
        order.setProviderId(domain.getProviderId());
        }
        order.setRetryStatus(domain.getRetryStatus());
        ResultDO result = goodsService.getGoodsByGoodsNo(order.getGoodsNo(), order.getAmount(), order.getRechargeNumber());
        GoodsDO goods = (GoodsDO) result.getModel(ResultDO.FIRST_MODEL_KEY);
        order.setTitle(goods.getTitle());
        order.setGoodsId(goods.getId());
        order.setProvinceName(goods.getProvinceName());
        order.setCityName(goods.getCityName());
        order.setServiceProvider(goods.getServiceProvider());
        order.setGoodsType(goods.getType());

        result = orderPay(order);
        if (!result.isSuccess()) {
            errorCode = result.getErrorCode();
            logger.info(errorCode.getMsg());
        }
        if (errorCode == null) {
            order = (OrderDO) result.getModel(ResultDO.FIRST_MODEL_KEY);
            sendExecService.execute(new CommitThread(order));
        }

        if (errorCode != null) {
            error(order, errorCode);
        }
       });
    }

    private void error(OrderDO order, ErrorCode errorCode) {
        TransactionDO transaction = new TransactionDO();
        transaction.setStatus(Constant.TRANSACTION_STATUS_0);
        order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
        order.setProviderCallbackInfo(errorCode.getMsg());
        updateStatus(transaction, order);
        if(errorCode != null) {
            merchantService.callback(
                    order.getMerchantSerialNumber(),
                    order.getOrderNo(),
                    order.getRechargeNumber(),
                    "",
                    errorCode.getCode(),
                    errorCode.getMsg(),
                    order.getCallback());
        }
    }

    public ResultDO getChannel(OrderDO order) {
        ResultDO result = getOrderGoodsChannel(order);
        if (!result.isSuccess()) {
            return result;
        }
        List<GoodsChannelDO> goodsChannels = (List<GoodsChannelDO>) result.getModel(ResultDO.FIRST_MODEL_KEY);
        int prioritySum = (int) result.getModel(ResultDO.SECOND_MODEL_KEY);
        if (prioritySum != 0) {
            priority(goodsChannels, prioritySum, order.getGoodsNo());
            for (int i = 0; i < goodsChannels.size(); i++) {
                Integer currentBatchCount = (Integer) redisUtil.get(order.getGoodsNo() + ":" + goodsChannels.get(i).getProviderNo());
                if (currentBatchCount > 0) {
                    currentBatchCount--;
                } else {
                    goodsChannels.remove(i);
                }
                redisUtil.set(order.getGoodsNo() + ":" + goodsChannels.get(i).getProviderNo(), currentBatchCount);
            }
        }
        order.setGoodsChannels(goodsChannels);
        result.setModel(ResultDO.FIRST_MODEL_KEY, order);
        return result;
    }


    class CommitThread implements Runnable {

        private OrderDO order;

        public CommitThread(OrderDO order) {
            this.order = order;
        }

        public void run() {
            long beginTime = System.currentTimeMillis();
            ErrorCode errorCode = null;
            TransactionDO transaction = new TransactionDO();
            int code = 0;

            ResultDO result = getChannel(order);
            if(!result.isSuccess()) {
                order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
                errorCode = result.getErrorCode();
            }

            if(errorCode == null) {
                for (int index = 0; index < order.getGoodsChannels().size(); index++) {
                    GoodsChannelDO channel = order.getGoodsChannels().get(index);
                    order.setProviderSerialNumber(SnowflakeIdWorker.getNo());
                    channel.setProviderSerialNumber(order.getProviderSerialNumber());
                    if (channel.getLossType() == Constant.LOSS_TYPE_2) {
                        if (order.getAgentDiscount() < channel.getCostDiscount()) {
                            errorCode = ErrorCode.API_GOODS_DISCOUNT_ERROR;
                            order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
                            continue;
                        }
                    }
                    order.setGoodsChannelDO(channel);
                    order.setProviderId(channel.getProviderId());
                    order.setProviderNo(channel.getProviderNo());
                    order.setProviderName(channel.getProviderName());
                    order.setCostDiscount(channel.getCostDiscount());
                    CardVoucherDO cardVouchere = null;
                    if (isCommit(order, channel)) {
                        if (StringUtils.isEmpty(order.getCardNo()) && StringUtils.isEmpty(order.getCardPassword())) {
                            if (channel.getProviderMethod().intValue() == Constant.PROVIDER_METHOD_1) {
                                result = cardVoucherService.getCardByGoodsNo(order.getGoodsNo());
                                if (!result.isSuccess()) {
                                    errorCode = ErrorCode.API_CARD_VOUCHER_ERROR;
                                    logger.info(errorCode.getMsg());
                                    order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
                                    break;
                                }
                                cardVouchere = (CardVoucherDO) result.getModel(ResultDO.FIRST_MODEL_KEY);
                                order.setCardNo(cardVouchere.getVoucherNo());
                                order.setCardPassword(cardVouchere.getVoucherPassword());
                            }
                        }
                        order = call(order);
                    }
                    if (order.getStatus().intValue() == Constant.RECHARGE_ORDER_STATUS_5
                            || order.getStatus().intValue() == Constant.RECHARGE_ORDER_STATUS_7) {
                        if (channel.getProviderMethod().intValue() == Constant.PROVIDER_METHOD_1) {
                            cardVouchere.setStatus(Constant.CARDVOUCH_STATUS_PROCESS);
                            cardVouchere.setProviderSerialNumber(order.getProviderSerialNumber());
                            cardVoucherService.editStatus(cardVouchere);
                        }
                        errorCode = null;
                        break;
                    } else if (channel.getProviderMethod().intValue() == Constant.PROVIDER_METHOD_1) {
                        if (order.getCardStatus().intValue() == Constant.BUSINESS_RECHARGE_RETURN_STATUS_CARD_INVALID) {
                            cardVouchere.setStatus(Constant.CARDVOUCH_STATUS_INVALID);
                            //卡密无效回调供卡商
                            result = cardVoucherService.cardCallback(cardVouchere);
                            String returnCode = (String) result.getModel(ResultDO.FIRST_MODEL_KEY);
                            VoucherRecordDO voucherRecordDO = new VoucherRecordDO();
                            voucherRecordDO.setCardVoucherId(cardVouchere.getId());
                            voucherRecordDO.setOrderId(order.getId());
                            voucherRecordDO.setOrderNo(order.getOrderNo());
                            voucherRecordDO.setProviderId(cardVouchere.getProviderId());
                            voucherRecordDO.setStatus(Constant.CARDVOUCH_STATUS_INVALID);
                            voucherRecordDO.setReturnCode(returnCode);
                            cardVoucherService.editStatus(cardVouchere);
                            voucherRecordService.add(voucherRecordDO);
                            order.setCardNo(null);
                            order.setCardPassword(null);
                        }
                    }
                    logger.info("调用上游接口返回的code：" + code);
                }
            }

            transaction.setAmount(order.getRealAmount());
            transaction.setBeforeBalance(order.getBeforeBalance());
            transaction.setBalance(order.getAfterBalance());

            if (order.getStatus().intValue() == Constant.RECHARGE_ORDER_STATUS_4) {
                RefundDO refundDO = new RefundDO();
                refundDO.setMerchantNo(order.getMerchantNo());
                refundDO.setMerchantId(order.getMerchantId());
                refundDO.setAmount(Double.valueOf(String.valueOf(ArithUtils.mul(Double.parseDouble(String.valueOf(order.getAmount())), Double.parseDouble(String.valueOf(order.getAgentDiscount()))))).longValue());
                refundDO.setGoodsTitle(order.getTitle());
                refundDO.setMerchantName(order.getMerchantName());
                refundDO.setType(Constant.REFUND_TYPE_AUTO);
                refundDO.setStatus(Constant.REFUND_STATUS_0);
                refundService.submit(refundDO);
                transaction.setStatus(Constant.TRANSACTION_STATUS_0);
                order.setProviderCallbackInfo(errorCode.getMsg());
            }
            order.setProcessTime(System.currentTimeMillis() - beginTime);
            if (order.getRetryStatus().intValue() == Constant.ORDER_RETRY_STATUS_0) {
                updateStatus(transaction, order);
            }
            if (errorCode != null) {
                merchantService.callback(
                        order.getMerchantSerialNumber(),
                        order.getOrderNo(),
                        order.getRechargeNumber(),
                        "",
                        errorCode.getCode(),
                        errorCode.getMsg(),
                        order.getCallback());
            }
        }
    }

    private ResultDO updateStatus(TransactionDO transaction, OrderDO order) {
        transaction.setGoodsName(order.getTitle());
        transaction.setIdentifierNo(order.getOrderNo());
        transaction.setMerchantNo(order.getMerchantNo());
        transaction.setType(Constant.TRANSACTION_TYPE_3);
        transaction.setAmountType(Constant.EXPEND);
        String transactionNo = SnowflakeIdWorker.getNo();
        transaction.setTransactionNo(transactionNo);
        ResultDO result = transactionService.add(transaction);
        transaction = (TransactionDO) result.getModel(ResultDO.FIRST_MODEL_KEY);

        order.setProvideQuartity(1);
        order.setTransactionNo(transactionNo);
        order.setTransactionId(transaction.getId());

        orderService.add(order);

        logger.info(order.getOrderNo() + " 更新订单的充值结果：" + order.getStatus());
        return result;
    }

    private ResultDO orderPay(OrderDO order) {
        Long amount = 0L;
        Long beforeBalance = 0L;
        Long afterBalance = 0L;
        Float agentDiscount = 0.0F;

        ResultDO result = goodsMerchantService.getGoodsMerchantDiscount(order.getGoodsId(), order.getMerchantId());
        if (!result.isSuccess()) {
            logger.info("商品与代理商价格关联失败");
            return result;
        }

        agentDiscount = (Float) result.getModel(ResultDO.FIRST_MODEL_KEY);
        amount = (long) ArithUtils.mul((double) order.getAmount(), Double.parseDouble(agentDiscount.toString()));

        result = merchantAccountService.editBalance(order.getMerchantId(),  -amount);

        beforeBalance = (Long) result.getModel(ResultDO.FIRST_MODEL_KEY);
        afterBalance = (Long) result.getModel(ResultDO.SECOND_MODEL_KEY);
        if (!result.isSuccess()) {
            logger.info("订单支付扣款失败");
            result.setErrorCode(ErrorCode.API_USER_BALANCE_STATUS_ERROR);
            return result;
        }
        order.setRealAmount(amount);
        order.setBeforeBalance(beforeBalance);
        order.setAfterBalance(afterBalance);
        order.setAgentDiscount(agentDiscount);
        result.setModel(ResultDO.FIRST_MODEL_KEY, order);
        return result;
    }

    private boolean isCommit(OrderDO orderDO, GoodsChannelDO channel) {
        //路由黑名单
        ResultDO result = routeFilterService.getRouterByProviderAndMerchantId(channel.getProviderId(), orderDO.getMerchantId(), 1);
        if (!result.isSuccess()) {
            logger.info("通道是该商品黑名单");
            return false;
        }
        if(orderDO.getRetryStatus() == Constant.ORDER_RETRY_STATUS_0) {
            //查询是否改订单已经调用该通道并且失败
            result = providerOrderService.getProviderOrderFail(orderDO.getOrderNo(), channel.getProviderId());
            if (!result.isSuccess()) {
                logger.info("通道有失败的供应单");
                return false;
            }
        }
        return true;
    }

    private OrderDO call(OrderDO order) {

        int status = 0;
        int code = 0;
        String message = "";
        boolean isWriter = true;
        long providerTime = System.currentTimeMillis();
        ResultDO result = new ResultSupport();
        GoodsChannelDO goodsChannelDO = order.getGoodsChannelDO();
        logger.info("当前供应商：" + goodsChannelDO.getProviderName());
        goodsChannelDO.setAmount(order.getAmount());
        goodsChannelDO.setOrderNo(order.getOrderNo());
        goodsChannelDO.setServiceProvider(order.getServiceProvider());
        if(StringUtils.isNotEmpty(order.getCardNo())) {
            goodsChannelDO.setCardNo(order.getCardNo());
            goodsChannelDO.setCardPassword(order.getCardPassword());
        }
        //当前发送量小于阀值时可以继续发送
        try {
            long beginTime = System.currentTimeMillis();
            result = providerApiService.commitOrder(goodsChannelDO);
            logger.info("---------------发送HTTP请求返回用时"  + order.getOrderNo() + " : " + (System.currentTimeMillis() - beginTime));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            code = (int) result.getModel(ResultDO.FIRST_MODEL_KEY);
            message = (String) result.getModel(ResultDO.SECOND_MODEL_KEY);
        }

        int isOver = Constant.PROVIDER_ORDER_IS_OVER_0;
        if (code == Constant.BUSINESS_RECHARGE_RETURN_STATUS_SUCCESS) {
            order.setStatus(Constant.RECHARGE_ORDER_STATUS_5);
            status = Constant.PROVIDER_ORDER_STATUS_3;
        } else if (code == Constant.BUSINESS_RECHARGE_RETURN_STATUS_OUT_TIME) {
            order.setStatus(Constant.RECHARGE_ORDER_STATUS_7);
            status = Constant.PROVIDER_ORDER_STATUS_4;
        } else if (code == Constant.BUSINESS_RECHARGE_RETURN_STATUS_FAIL) {
            isOver = Constant.PROVIDER_ORDER_IS_OVER_1;
            status = Constant.PROVIDER_ORDER_STATUS_fail;
            order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
        } else if(code == Constant.BUSINESS_RECHARGE_RETURN_STATUS_CARD_INVALID) {
            order.setCardStatus(Constant.BUSINESS_RECHARGE_RETURN_STATUS_CARD_INVALID);
            order.setStatus(Constant.RECHARGE_ORDER_STATUS_4);
            //卡密无效不写供应单
            isWriter = false;
        }

        if(isWriter) {
            ProviderOrderDO providerOrder = new ProviderOrderDO();
            BeanUtilsExtends.copyProperties(providerOrder, order);
            providerOrder.setId(null);
            providerOrder.setOrderId(order.getId());
            providerOrder.setCardVoucherNo(order.getCardNo());
            providerOrder.setVoucherNo(order.getCardNo());
            providerOrder.setGoodsName(order.getTitle());
            providerOrder.setSerialNumber(order.getProviderSerialNumber());
            providerOrder.setProviderId(goodsChannelDO.getProviderId());
            providerOrder.setProviderNo(goodsChannelDO.getProviderNo());
            providerOrder.setProviderMethod(goodsChannelDO.getProviderMethod());
            providerOrder.setProviderName(goodsChannelDO.getProviderName());
            providerOrder.setProcessTime(System.currentTimeMillis() - providerTime);
            providerOrder.setCostDiscount(goodsChannelDO.getCostDiscount());
            providerOrder.setIsManual(Constant.MANUAL_PROVIDER_ORDER_STATUS_0);
            providerOrder.setIsRemedy(Constant.REMEDY_PROVIDER_ORDER_STATUS_0);
            providerOrder.setIsOver(isOver);
            providerOrder.setStatus(status);
            providerOrder.setRemark(message);
            providerOrderService.add(providerOrder);
        }

        logger.info("订单提交上游结果 status:" + status + " code:" + code);
        return order;
    }

    private ResultDO getOrderGoodsChannel(OrderDO domain) {

        ResultDO result = goodsService.getGoodsChannel(domain);
        if(!result.isSuccess()) {
            //logger.info("订单去查找相应通道失败");
            return result;
        }
        List<GoodsChannelDO> goodsChannelArray = (List<GoodsChannelDO>) result.getModel(ResultDO.FIRST_MODEL_KEY);
        List<GoodsChannelDO> goodsChannels = new ArrayList<>();
        goodsChannels.addAll(goodsChannelArray);

        if(domain.getRetryStatus().intValue() == Constant.ORDER_RETRY_STATUS_2) {
            //logger.info("重试当前的通道");
            boolean is = false;
            goodsChannels.clear();
            for(GoodsChannelDO goodsChannelDO : goodsChannelArray) {
                if(goodsChannelDO.getProviderId().equals(domain.getProviderId())) {
                    //logger.info("之前使用了该通道就从这里开始");
                    is = true;
                }
                if(is) {
                    goodsChannels.add(goodsChannelDO);
                }
            }
        }
        if(domain.getRetryStatus().intValue() == Constant.ORDER_RETRY_STATUS_3) {
            //logger.info("重试当前的通道的下一个通道");
            boolean is = false;
            goodsChannels.clear();
            for(GoodsChannelDO goodsChannelDO : goodsChannelArray) {
                if(goodsChannelDO.getProviderId().equals(domain.getProviderId())) {
                    //logger.info("之前使用了该通道就从这里的下一个开始");
                    is = true;
                    continue;
                }
                if(is) {
                    goodsChannels.add(goodsChannelDO);
                }
            }
        }
        if (goodsChannels.size() == 0) {
            result.setSuccess(false);
            result.setErrorCode(ErrorCode.API_GOODS_NOT_EXITE_ERROR);
           return result;
        }
        int prioritySum = 0;
        for (GoodsChannelDO goodsChannelDO : goodsChannels) {
            prioritySum += goodsChannelDO.getPriority();
        }
        result.setModel(ResultDO.FIRST_MODEL_KEY, goodsChannels);
        result.setModel(ResultDO.SECOND_MODEL_KEY, prioritySum);
        return result;
    }

    private void priority(List<GoodsChannelDO> goodsChannels, int prioritySum, int goodsNo) {
        for(GoodsChannelDO goodsChannelDO : goodsChannels) {
            int currentBatchCount = 0;
            logger.info("当前通道：" + goodsChannelDO.getProviderName() + " 权重值为：" + goodsChannelDO.getPriority() + " prioritySum:" + prioritySum);
            //该产品下面权重不为0的通道进行权重计算
            double ratio = (goodsChannelDO.getPriority().intValue() * 0.1) / (prioritySum * 0.1);
            logger.info("计算出来的权重量：" + ratio);
            //得到当前通道已发送数量
            currentBatchCount = (int) Math.round(maxOrderThreadNumber * ratio);
            //权重计算得到可以发送量
            logger.info("当前通道：" + goodsChannelDO.getProviderName() + " 分配的量为：" + currentBatchCount);
            //发送数量大于本批次可以发送数量时跳过该通道走下一个通道
            redisUtil.set(goodsNo + ":" + goodsChannelDO.getProviderNo(), currentBatchCount);
        }
    }

}